<!DOCTYPE html>
<html xmlns:ng="http://angularjs.org" data-ng-app="MosaicApp">
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
    <meta charset="UTF-8">
    <title>Mosaic</title>
    <script src="Mosaic.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js"></script>



    <script>

"use strict";


//i+=++i, this sequence gives 2^n - 1, cool


/*
TIMING
var timings = [];
timings.push({pn:performance.now(), info:'STARTING POINT', delta:0});
timings.push({pn:performance.now(), info:'WHAT DID I DO, OR AM I GOING TO DO?', delta:performance.now() - timings[timings.length - 1].pn});
 */



var SQUARE_SIZE = 100;
var rawImagedata;
var tileImagedata = [];
var filesArray;



/*
function mix() {
console.log('foo');
  for (var i = 0; i < newPicData.length; ++i) {
    var closest;
    var distance = Number.MAX_VALUE;

    for (var j = 0; j < tileData.length; ++j) {
      var temp;
      if ((temp = Math.pow(newPicData[i].r - tileData[j].r, 2) + 
          Math.pow(newPicData[i].g - tileData[j].g, 2) +
          Math.pow(newPicData[i].b - tileData[j].b, 2)) < distance) {
        distance = temp;
        closest = tileData[j];
      }
    }

    //print to canvas
    var canvas = document.getElementById('c');
    var ctx = canvas.getContext('2d');
    var img = new Image();
    img.src = closest.src;
    // Closure to capture the file information. cf. http://www.html5rocks.com/en/tutorials/file/dndfiles/
    img.onload = (function(i, img) {
      return function(e) {
        var sx = 0,sy=0,sw=Math.min(img.height,img.width),sh=Math.min(img.height,img.width),dx=newPicData[i].col,dy=newPicData[i].row,dw=SQUARE_SIZE,dh=SQUARE_SIZE;
        ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);
      }
    })(i, img);
  }
}

*/



angular.module('MosaicApp', []).controller('MosaicController', ['$scope', function($scope) {
  $scope.origName = '';
  $scope.origHeight = 0;
  $scope.origWidth = 0;
  $scope.numTileImgs = 0;
  $scope.tileImgs = null;
  $scope.tileImgsCnt = 0;
  $scope.tileImgsData = [];
  $scope.tileImgsCurrentName = '';
  $scope.a = null;
  $scope.b = null;
  $scope.outHeight = 0;
  $scope.outWidth = 0;
  $scope.newImgData = [];



  function mix(newPicData, tileData, canvas, ctx, b) {
    $scope.i = 0;

    var foo = function() {
      if ($scope.i == newPicData.length) {
        return;
      }
      var closest = null;
      var distance = Number.MAX_VALUE;
      for (var j = 0; j < tileData.length; ++j) {
        var temp;
        if ((temp = Math.pow(newPicData[$scope.i].r - tileData[j].r, 2) + 
            Math.pow(newPicData[$scope.i].g - tileData[j].g, 2) +
            Math.pow(newPicData[$scope.i].b - tileData[j].b, 2)) < distance) {
          distance = temp;
          closest = tileData[j];
        }
      }

      //print to canvas
      var img = new Image();
      img.src = closest.src;
      // Closure to capture the file information. cf. http://www.html5rocks.com/en/tutorials/file/dndfiles/
      img.onload = (function(i, img) {
        return function(e) {
          var sx = Math.floor((img.width - Math.min(img.height, img.width)) / 2),
              sy = Math.floor((img.height - Math.min(img.height, img.width)) / 2),
              sw = Math.min(img.height, img.width),
              sh = Math.min(img.height, img.width),
              dx = newPicData[i].col * b,
              dy = newPicData[i].row * b,
              dw = b,
              dh = b;
          ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);
          setTimeout(foo, 1);
        }
      })($scope.i++, img);
    }

    setTimeout(foo, 1);
  
    return;
  
    for (var i = 0; i < newPicData.length; ++i) {
      var closest = null;
      var distance = Number.MAX_VALUE;

      for (var j = 0; j < tileData.length; ++j) {
        var temp;
        if ((temp = Math.pow(newPicData[i].r - tileData[j].r, 2) + 
            Math.pow(newPicData[i].g - tileData[j].g, 2) +
            Math.pow(newPicData[i].b - tileData[j].b, 2)) < distance) {
          distance = temp;
          closest = tileData[j];
        }
      }

      //print to canvas
      var img = new Image();
      img.src = closest.src;
      // Closure to capture the file information. cf. http://www.html5rocks.com/en/tutorials/file/dndfiles/
      img.onload = (function(i, img) {
        return function(e) {
          var sx = Math.floor((img.width - Math.min(img.height, img.width)) / 2),
              sy = Math.floor((img.height - Math.min(img.height, img.width)) / 2),
              sw = Math.min(img.height, img.width),
              sh = Math.min(img.height, img.width),
              dx = newPicData[i].col * b,
              dy = newPicData[i].row * b,
              dw = b,
              dh = b;
          ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);
        }
      })(i, img);
    }
    
    
    
  }



  var getImageData = function(f, w, b) {
    var url =  window.URL.createObjectURL(f);
    var img = new Image();
    img.src = url;
    img.onload = function(e) {
      var canvas = document.createElement('canvas');
      var ctx = canvas.getContext('2d');
      canvas.height = b;
      canvas.width = b;
      var sx = Math.floor((img.width - Math.min(img.height, img.width)) / 2),
          sy = Math.floor((img.height - Math.min(img.height, img.width)) / 2),
          sw = Math.min(img.height, img.width),
          sh = Math.min(img.height, img.width),
          dx = 0,
          dy = 0,
          dw = b,
          dh = b;

      ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);
      var id = ctx.getImageData(0, 0, b, b);
  
      w.postMessage({src: img.src, id: id});
  
      img = null;
      canvas = null;
      ctx = null;
  
      //callback(id);
    }
  }



  $scope.loadBaseImage = function(file) {
    console.log('Loading base file.');

    $scope.origName = file.name;
    $scope.$apply();

    var canvas = document.createElement('canvas');
    var ctx = canvas.getContext('2d');
    var img = new Image();

    console.log('Base file named [' + file.name + ']');

    $scope.origSrc = img.src = window.URL.createObjectURL(file);
    img.onload = function(e) {  //no closure needed, only 1 file
      $scope.origHeight = canvas.height = img.height;
      $scope.origWidth = canvas.width = img.width;
      $scope.$apply();
      ctx.drawImage(img,0,0);
      rawImagedata = ctx.getImageData(0, 0, img.width, img.height);

      console.log('Base image data loaded.');
    }
  }



  $scope.loadTileImages = function(files) {
    $scope.numTileImgs = files.length;
    console.log($scope.numTileImgs + ' files selected.');
    $scope.tileImgs = files;
    $scope.$apply();
  }



  $scope.processMosaic = function() {
    var numCols = Math.floor($scope.origWidth / $scope.a);
    var numRows = Math.floor($scope.origHeight / $scope.a);
    var vo = Math.floor(($scope.origHeight % $scope.a) / 2);  //vertical offset to account for pixelation
    var ho = Math.floor(($scope.origWidth % $scope.a) / 2);  //horizontal offset to account for pixelation
    var numPixels = Math.pow($scope.a, 2);
    var canvas = document.getElementById('bob');
    var ctx = canvas.getContext('2d');

    //TODO: magic

    //1) calculate new base image size
    //console.log(Math.floor((100 - (11*9) ) / 2),Math.ceil((100 - (11*9) ) / 2))
    
    canvas.width = $scope.outWidth = numCols * $scope.b;
    canvas.height = $scope.outHeight = numRows * $scope.b;
    //$scope.$apply();

    //2) calculate new base image averages
    //TODO how to handle for offset? e.g. 100x100 original image with 30px sample area? output should be 90x90
    for (var col = 0; col < numCols; ++col) {
      for (var row = 0; row < numRows; ++row) {
        var r = 0, g = 0, b = 0;
        var offset = ho + vo + row * numPixels * numCols *4 + col * 4 * $scope.a;
        for (var i = 0; i < $scope.a; ++i) {
          for (var j = 0; j < $scope.a; ++j) {
            //rawImagedata.data
            var subOffset = offset + i * $scope.a * numCols * 4 + j * 4;
            r += rawImagedata.data[subOffset];
            g += rawImagedata.data[subOffset + 1];
            b += rawImagedata.data[subOffset + 2];
          }
        }

        r /= numPixels;
        g /= numPixels;
        b /= numPixels;

        r = parseInt(r);
        g = parseInt(g);
        b = parseInt(b);

        ctx.fillStyle = 'rgb(' + r + ', ' + g + ' , ' + b + ')';
        ctx.fillRect(col * $scope.b, row * $scope.b, $scope.b, $scope.b);
        $scope.newImgData.push({col: col, row: row, r: r, g: g, b: b});
      }
    }

    //3) calculate tile averages for center square at new tile size
    var w = new Worker('Mosaic.js');
    w.onmessage = function(e) { $scope.tileImgsData.push(e.data); setTimeout(doThing, 1); }

    var doThing = function() {
      var f;
      if (f = $scope.tileImgs.item($scope.tileImgsCnt++)) {
        $scope.tileImgsCurrentName = f.name;
        $scope.$apply();
        getImageData(f, w, $scope.b);
      } else {
        console.log('Completed tile calculations.  Now mixing tiles into original image.');
        mix($scope.newImgData, $scope.tileImgsData, canvas, ctx, $scope.b);
      }
    }

    setTimeout(doThing, 1);

    //4) make mosaic
    

  }



}]).config(['$compileProvider', function($compileProvider) {
  $compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|file|blob):|data:image\//);
}]);




    </script>

    <style>
      body{
          background: none repeat scroll 0 0 #CCCCCC;
          margin: 0;
          font-family:sans-serif;
      }
      img{vertical-align:middle;}
      .box{
          background: none repeat scroll 0 0 #FFFFFF;
          border: 1px solid #666666;
          border-radius: 5px;
          box-shadow: 0 0 2px 2px #999999;
          margin: 1em;
          padding: 1em;
      }
      .done,.waiting{font-size:8em;vertical-align:middle;}
      .done{color:#0f0;}
      .waiting{color:#ddd;}
    </style>
  </head>
  <body data-ng-controller="MosaicController">

    <div class="box">
      <span ng-show="origName == ''">
        <span class="waiting">&#x2714</span>
        <input type="file" accept="image/*" ng-model="photo" onchange="angular.element(this).scope().loadBaseImage(this.files[0])" />
      </span>
      <span ng-hide="origName == ''">
        <span class="done">&#x2714</span>
        <img ng-src="{{origSrc}}" height="100" />
        {{origName}} ({{origWidth}} x {{origHeight}})
      </span>
    </div>

    <div class="box">
      <span ng-hide="numTileImgs > 0">
        <span class="waiting">&#x2714</span>
        <input type="file" accept="image/*" multiple onchange="angular.element(this).scope().loadTileImages(this.files)" />
      </span>
      <span ng-show="numTileImgs > 0">
        <span class="done">&#x2714</span>
        <span>{{numTileImgs}} candidate tile images have been selected.</span>
      </span>
    </div>

    <div class="box">
      <span class="waiting" ng-show="a < 1 || b < 1">&#x2714</span>
      <span class="done" ng-show="a > 0 && b > 0">&#x2714</span>
      <label for="inBase">Original Image Sample Area (n x n).  For now, a divisor of both the width and height must be used.</label>
      <input id="inBase" type="number" ng-model="a" placeholder="n" />
      <br />
      <label for="inTile">Tile Image Output Size (n x n)</label>
      <input id="inTile" type="number" ng-model="b" placeholder="n" />
    </div>
    
    <div class="box">
      <input type="button" value="Generate Mosaic" ng-click="processMosaic()" ng-disabled="origName == '' || numTileImgs == 0 || a < 1 || b < 1" />
      <span ng-hide="tileImgsCurrentName == ''">
        {{tileImgsCnt}}
        &mdash;
        {{tileImgsCurrentName}}
      </span>
    </div>

    <canvas id='bob'></canvas>

  </body>
</html>